﻿using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace SpaceGame.Util
{
    /// <summary>
    /// Represent a vector-based polygon.
    /// </summary>
    public class Polygon : IEnumerable<Vector2>
    {
        public Polygon(params Vector2[] points)
        {
            N = points.Length;
            Points = points;
        }

        public int N
        {
            get; private set;
        }

        private float? area;
        private Vector2? centroid;
        public readonly Vector2[] Points;
        

        /// <summary>
        /// Find the area of this polygon.
        /// </summary>
        /// <returns></returns>
        /// <remarks>from wikipedia</remarks>
        public float Area 
        {
            get
            {
                if (area.HasValue)
                    return area.Value;

                area = 0;

                for (int i = 0; i < N - 1; i++)
                {
                    Vector2 p1 = this[i];
                    Vector2 p2 = this[i + 1];
                    area += (p1.X * p2.Y - p2.X * p1.Y);
                }

                return area.Value / 2;
            }
        }

        public Vector2 Centroid
        {
            get
            {
                Vector2 c = Vector2.Zero;

                for (int i = 0; i < N - 1; i++)
                {
                    Vector2 p1 = this[i];
                    Vector2 p2 = this[i + 1];

                    float common = (p1.X*p2.Y - p2.X*p1.Y);
                    c.X += (p1.X + p2.X)*common;
                    c.Y += (p1.Y + p2.Y)*common;
                }

                float factor = 6*Area;
                c.X /= factor;
                c.Y /= factor;

                centroid = c;
                return centroid.Value;
            }
        }
        
        /// <summary>
        /// Check if point is inside polygon. 
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        /// <remarks>Code was taken java Polygon class.</remarks>
        public bool ContainsPoint(Vector2 point)
        {
            int i, j; bool c = false;
            for (i = 0, j = N - 1; i < N; j = i++)
            {
                if ((((this[i].Y <= point.Y) && (point.Y < this[j].Y)) ||
                     ((this[j].Y <= point.Y) && (point.Y < this[i].Y))) &&
                    (point.X < (this[j].X - this[i].X) * (point.Y - this[i].Y) / (this[j].Y - this[i].Y) + this[i].X))

                    c = !c;
            }

            return c;

			#region commented
			/*
            int hits = 0;

            Vector2 last = this[N - 1];
            Vector2 current = Vector2.Zero;

            // Walk the edges of the polygon
            for (int i = 0; i < N; last = current, i++)
            {
                current = this[i];
                
                if (current.Y == last.Y)
                {
                    continue;
                }

                float leftx;
                if (current.X < last.X)
                {
                    if (point.X >= last.X)
                    {
                        continue;
                    }
                    leftx = current.X;
                }
                else
                {
                    if (point.X >= current.X)
                    {
                        continue;
                    }
                    leftx = last.X;
                }

                double test1, test2;
                if (current.Y < last.Y)
                {
                    if (point.Y < current.Y || point.Y >= last.Y)
                    {
                        continue;
                    }
                    if (point.X < leftx)
                    {
                        hits++;
                        continue;
                    }
                    test1 = point.X - current.X;
                    test2 = point.Y - current.Y;
                }
                else
                {
                    if (point.Y < last.Y || point.Y >= current.Y)
                    {
                        continue;
                    }
                    if (point.X < leftx)
                    {
                        hits++;
                        continue;
                    }
                    test1 = point.X - last.X;
                    test2 = point.Y - last.Y;
                }

                if (test1 < (test2 / (last.Y - current.Y) * (last.X - current.X)))
                {
                    hits++;
                }
            }
            return ((hits & 1) != 0);
            */
			#endregion
		}

        /// <summary>
        /// If one exists, detect a boundary between two polygons.
        /// 
        /// </summary>
        /// <param name="polygon"></param>
        /// <param name="boundary"></param>
        /// <returns></returns>
        /// <remarks>TODO: this method is not complete yet.</remarks>
        public bool FindBoundary(Polygon polygon, out Boundary boundary)
        {
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < polygon.N; j++)
                {
                    if (this[i] == polygon[j])
                    {
                        int pi = i == 0 ? N - 1 : i - 1;
                        int ni = (i + 1) % N;

                        int pj = j == 0 ? polygon.N - 1 : j - 1;
                        int nj = (j + 1) % polygon.N;

                        if (this[pi] == polygon[pj] || this[pi] == polygon[nj])
                        {
                            boundary = new Boundary(this[i], this[pi]);
                            return true;
                        }
                        if (this[ni] == polygon[pj] || this[ni] == polygon[nj])
                        {
                            boundary = new Boundary(this[i], this[ni]);
                            return true;
                        }
                    }
                }
            }
            boundary = Boundary.Empty;
            return false;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Polygon))
                return false;

            Polygon otherPolygon = (Polygon)obj;

            foreach (Vector2 point in Points)
            {
				bool match = ((IList)otherPolygon.Points).Contains(point); //Array.Exists(otherPolygon.Points, p => p.Equals(point));
                if (!match)
                    return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        #region IEnumerator
        public Vector2 this[int index]
        {
            get
            {
                return Points[index];
            }
        }

        public IEnumerator<Vector2> GetEnumerator()
        {
            return new List<Vector2>(Points).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return Points.GetEnumerator();
        }
        #endregion
    }
}
